package com.rpcfx.server;

import com.rpcfx.api.Filter;
import com.rpcfx.api.FilterChain;
import com.rpcfx.api.FilterChainImpl;
import com.rpcfx.api.Invoker;
import com.rpcfx.api.RpcfxException;
import com.rpcfx.api.RpcfxRequest;
import com.rpcfx.api.RpcfxResponse;
import com.rpcfx.api.ServiceLoader;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;


/**
 * @ProjectName: learning
 * @Package: com.rpcfx.server
 * @ClassName: ObjectFactory
 * @Description:
 * @Date: 2021/11/11 10:46 下午
 * @Version: 1.0
 */
public final class ObjectFactory {

    private static final List<BeanContainer> beanContainers = new ArrayList<>();

    static {
        List<BeanContainer> containers = ServiceLoader.load(BeanContainer.class);
        beanContainers.addAll(containers);
    }

    public static <T> T getObject(Class<T> tClass) {
        T obj = null;
        for (BeanContainer beanContainer : beanContainers) {
            obj = beanContainer.getBean(tClass);
            if (obj != null) {
                break;
            }
        }
        return obj;
    }


    public static <T> T getProxy(Class<T> sourceInterface, String url) throws IllegalAccessException, InstantiationException {
        List<String> methodNameds = Arrays.stream(sourceInterface.getDeclaredMethods()).map(Method::getName).collect(Collectors.toList());
        ByteBuddy byteBuddy = new ByteBuddy(ClassFileVersion.JAVA_V8);
        Object newInstance = byteBuddy.subclass(Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS)
                .implement(sourceInterface)
                .method(getMatcherMethod(methodNameds))
                .intercept(InvocationHandlerAdapter.of(new RpcInvocationHandler(url, sourceInterface)))
                .make().load(ClassLoader.getSystemClassLoader()).getLoaded().newInstance();

        return (T) newInstance;

    }

    private static ElementMatcher<? super MethodDescription> getMatcherMethod(List<String> methods) {
        if (methods == null || methods.size() == 0) {
            return ElementMatchers.any();
        }
        ElementMatcher.Junction<MethodDescription> elementMatcher = ElementMatchers.none();
        for (String methodName : methods) {
            elementMatcher = elementMatcher.or(ElementMatchers.named(methodName));
        }
        return elementMatcher;
    }

    public static class RpcInvocationHandler implements InvocationHandler {

        private String url;

        private Class<?> interfaceClass;

        private final FilterChain filterChain;


        public RpcInvocationHandler(String url, Class<?> interfaceClass) {
            this.url = url;
            this.interfaceClass = interfaceClass;
            List<Filter> filters = ServiceLoader.load(Filter.class);
            filterChain = new FilterChainImpl();
            if (filters.size() > 0) {
                filters.forEach(filterChain::addFilter);
            }

        }

        public Object invoke(Object proxy, Method method, Object[] args) {

            RpcfxRequest request = new RpcfxRequest();
            request.setServiceClass(this.interfaceClass.getName());
            request.setMethod(method.getName());
            request.setParams(args);
            filterChain.filter(request);
            Invoker invoker = new Invoker(interfaceClass, url);
            RpcfxResponse result = invoker.invoke(method, args);
            if (result.getRpcfxException() != null) {
                throw result.getRpcfxException();
            }
            if (result.isSuccess()) {
                return result.getData();
            }
            throw new RpcfxException(-1, "远程调用失败");
        }
    }
}
